﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interpreter
{
	internal static class Interpreter
	{
		public const int BC_ACTIVE_EXCEPTION = 1;
		public const int BC_ADD = 2;
		public const int BC_APPLY_ARG_N = 3;
		public const int BC_ARG_CHECK = 4;
		public const int BC_ASSIGN = 5;
		public const int BC_BASE_CLASS_CONS = 6;
		public const int BC_BITWISE_AND = 7;
		public const int BC_BITWISE_OR = 8;
		public const int BC_BITWISE_XOR = 9;
		public const int BC_BOOLEAN_NOT = 10;
		public const int BC_BRACKET = 11;
		public const int BC_BRACKET_WRAPPED = 12;
		public const int BC_CLASS = 13;
		public const int BC_CLASS_INIT_COMPLETE = 14;
		public const int BC_CONSTRUCTOR_CALL = 15;
		public const int BC_CREATE_LIST = 16;
		public const int BC_CREATE_MAP = 17;
		public const int BC_DIVIDE = 18;
		public const int BC_DOT = 19;
		public const int BC_DOT_WRAPPED = 20;
		public const int BC_EQUALS = 21;
		public const int BC_FALSE = 22;
		public const int BC_FLOAT = 23;
		public const int BC_FUNCTION_CALL = 24;
		public const int BC_GREATER_THAN = 25;
		public const int BC_GREATER_THAN_OR_EQUAL = 26;
		public const int BC_INCREMENT_PREFIX = 27;
		public const int BC_INCREMENT_POSTFIX = 28;
		public const int BC_INSTANCE_FIELD_ASSIGN = 29;
		public const int BC_INTEGER = 30;
		public const int BC_INVOKE_STATIC_CONSTRUCTOR = 62;
		public const int BC_JUMP = 31;
		public const int BC_JUMP_IF_FALSE = 32;
		public const int BC_JUMP_IF_FALSE_NO_POP = 33;
		public const int BC_JUMP_IF_NO_ARG_PRESENT = 34;
		public const int BC_JUMP_IF_TRUE = 35;
		public const int BC_JUMP_IF_TRUE_NO_POP = 36;
		public const int BC_KEEP_AND_DEREF_POINTER = 37;
		public const int BC_LESS_THAN = 38;
		public const int BC_LESS_THAN_OR_EQUAL = 39;
		public const int BC_MOD = 40;
		public const int BC_MULTIPLY = 41;
		public const int BC_NATIVE_FUNCTION = 42;
		public const int BC_NEGATIVE = 43;
		public const int BC_NOOP = 44;
		public const int BC_NOT_EQUAL = 45;
		public const int BC_NULL = 46;
		public const int BC_POP = 47;
		public const int BC_RETURN = 48;
		public const int BC_STACK_DATA = 49;
		public const int BC_ARG_PRE_VERIFY = 63;
		public const int BC_STRING = 50;
		public const int BC_SUBTRACT = 51;
		public const int BC_SWITCH = 52;
		public const int BC_THIS = 53;
		public const int BC_THROW = 54;
		public const int BC_THROW_BUBBLING = 55;
		public const int BC_TRUE = 56;
		public const int BC_TRY_POP = 57;
		public const int BC_TRY_PUSH = 58;
		public const int BC_VARIABLE = 59;
		public const int BC_VARIABLE_WRAPPED = 60;
		public const int BC_XOR = 61;

		public const int BC_MAX = 63;

		private const int PHASE2_PHUNCTION_NOOP = 0;
		private const int PHASE2_PHUNCTION_ASSIGN = 1;

		public const int VALUE_STACK_STARTING_ALLOCATION_SIZE = 8;

		/*
		public static string IdentifyID_DELETE_ME(Metadata metadata, int id)
		{
			string[] idTable = metadata.IdTable;
			if (id < idTable.Length)
			{
				return idTable[id];
			}
			return "INVALID ID#";
		}//*/

		public static int[] opCounter = new int[BC_MAX + 1];
		public static int cycles = 0;

		private static readonly Value[] EMPTY_VALUE_ARRAY = new Value[0];

		public static void Interpret(Metadata metadata, StackContext callStack)
		{
			int[] ops = metadata.OpCodes;
			int[] firstParam = metadata.OpFirstParam;
			int[][] multiParam = metadata.OpMultiParam;
			int startingPosition = metadata.StartingPosition;
			string[] idTable = metadata.IdTable;
			string[] stringTable = metadata.StringTable;
			int[] intTable = metadata.IntegerTable;
			double[] floatTable = metadata.FloatTable;
			Value[] classes = metadata.Classes;
			int[][] classBasicInfoTable = metadata.ClassesFast;
			int[][] constructorLookup = metadata.Constructors;
			int[] opMap = metadata.OpCodeMap;
			int[] byteCodeToOp = metadata.ByteCodeToOpEnum;
			Dictionary<int, int[]>[] classMemberLookupSet = metadata.ClassMemberLookupSet;
			Dictionary<int, int[]>[] staticClassMemberLookupSet = metadata.StaticClassMemberLookupSet;
			Dictionary<int, int[]>[] classMemberLookupGet = metadata.ClassMemberLookupGet;
			Dictionary<int, int[]>[] staticClassMemberLookupGet = metadata.StaticClassMemberLookupGet;
			int[][] staticPropertyGetterLookup = metadata.StaticPropertyGetters;
			int[][] staticPropertySetterLookup = metadata.StaticPropertySetters;
			int[][] propertyGetterLookup = metadata.PropertyGetters;
			int[][] propertySetterLookup = metadata.PropertySetters;
			int[] primitiveMethodLookup = metadata.PrimitiveMethodLookup;
			BuiltIns.NativeFunction[] nativeFunctions = metadata.NativeFunctions;
			Token[][] tokenTable = metadata.TokenInfo;
			bool callStackModified = false;

			Value[] integers = Value.INTEGERS;
			Value value = null;
			Value value1;
			Value value2;
			Value[] values, args;
			LocalVarBucket lvBucket, lvbWalker;
			int i, j, t, argCount;
			int num = 0;
			int int1, int2, int3;
			bool bool1, bool2, bool3, numset;
			string string1;
			StringBuilder stringBuilder;
			Value[] list1, list2, list3;
			MapImpl map1;
			int pc;
			int classId;
			int[] intArray;
			Value returnValue;
			Value assignmentTarget;
			Value f;
			Dictionary<int, Value> valueLookup1;
			StackContext callStack1;
			MethodSignature methodSignature;

			// TODO: replace the EMPTY_VALUE_ARRAY with an array of Value<String>s representing the command line args.
			StackContext callStackTop = callStack ?? new StackContext(null, startingPosition, null, EMPTY_VALUE_ARRAY, metadata);

			Value[] valueStack = callStackTop.ValueStack;
			int valueStackVirtualSize = callStackTop.ValueStackVirtualSize;
			int valueStackRealSize = callStackTop.ValueStackRealSize;

			List<int> pcs = new List<int>();

			while (true)
			{
				pc = callStackTop.ProgramCounter;

				//pcs.Insert(0, pc);

				++opCounter[ops[pc]];
				++cycles;

				// No ops (currently nor ever) will push more than two values. This allows for checking for quick equivalence
				// between the virtual stack size and actual stack size here to determine if the value stack array needs to be expanded.
				if (valueStackVirtualSize == valueStackRealSize)
				{
					valueStackRealSize = valueStackRealSize << 1;
					values = new Value[valueStackRealSize];
					for (int1 = 0; int1 < valueStackVirtualSize; ++int1)
					{
						values[int1] = valueStack[int1];
					}
					valueStack = values;
					callStackTop.ValueStack = valueStack;
					callStackTop.ValueStackRealSize = valueStackRealSize;
				}

				switch (ops[pc])
				{
					case Interpreter.BC_CLASS_INIT_COMPLETE:
						int1 = firstParam[pc]; // class ID that's being completed.
						value = classes[int1];
						if ((int)value.Value2 != 1)
						{
							throw new NotImplementedException("Why did this happen?");
						}

						int2 = metadata.ClassInitializationStack.Pop();
						if (int2 != int1)
						{
							throw new Exception("Popped value from class init stack did not match expected value from class init complete operation.");
						}

						value.Value2 = 2;
						break;

					case Interpreter.BC_THROW:
						value = valueStack[--valueStackVirtualSize]; // exception
						callStackTop = Interpreter.ThrowThis(metadata, callStackTop, value, false);
						callStackModified = true;
						break;

					case Interpreter.BC_TRY_PUSH:
						callStackTop.TryContext = new TryContext(firstParam[pc], multiParam[pc][0], callStackTop.TryContext);
						break;

					case Interpreter.BC_TRY_POP:
						// anything inside this if statement is allowed to be readable (slow) since it's an exception
						if (callStackTop.TryContext.BubblingException != null)
						{
							if (callStackTop.TryContext.BubblingExceptionCaught)
							{
								// exception has been caught. Proceed as normal.
							}
							else
							{
								// exception has not been caught. Keep bubbling.
								callStackTop = Interpreter.ThrowThis(metadata, callStackTop, callStackTop.TryContext.BubblingException, false);
								callStackModified = true;
							}
						}

						callStackTop.TryContext = callStackTop.TryContext.Next;

						break;

					case Interpreter.BC_ACTIVE_EXCEPTION:
						valueStack[valueStackVirtualSize++] = callStackTop.TryContext.BubblingException;
						break;

					case Interpreter.BC_APPLY_ARG_N:
						int1 = firstParam[pc]; // nth arg
						values = callStackTop.Args; // args
						if (int1 < values.Length)
						{
							valueStack[valueStackVirtualSize++] = values[int1];
						}
						else
						{
							int2 = multiParam[pc][0]; // default value ID if one exists, 0 otherwise

							if (int2 == 0)
							{
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Did not pass enough arguments to the function.", valueStackVirtualSize);
								callStackModified = true;
							}
							else
							{
								callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
								callStackTop = new StackContext(callStackTop, metadata.DefaultParameters[int2][0] - 1, callStackTop.ClassContext, EMPTY_VALUE_ARRAY, metadata);
								callStackModified = true;
							}
						}
						break;

					case Interpreter.BC_ARG_PRE_VERIFY:
						callStackTop.AddVerifiableArg(valueStack[valueStackVirtualSize - 1]);
						break;

					case Interpreter.BC_ARG_CHECK:
						/*
						 *  n + 2 values or more. 2nd value is n. 3rd+ is a list of type 
						 *  ID's (in CodeProvider.cs). If the args passed in to the current 
						 *  stack frame match these values, proceed, otherwise skip the offset 
						 *  provided as the first number. If the nth arg is of type class, 
						 *  the n+1 th arg is the required class ID or 0 if no specific type 
						 *  is required. This can make the length longer than n.
						 */
						intArray = multiParam[pc]; // multiparam value
						t = intArray[0]; // number of args for this matcher
						// bool1 represents if this is a match
						values = callStackTop.GetVerifiableArgs();
						if (t == values.Length)
						{
							int2 = intArray.Length; // length cache
							int1 = 0; // arg N
							bool1 = true;
							// t is now the iterator. We don't need it for the arg length anymore.
							for (t = 1; t < int2; ++t) // since class arguments have a type as an int (class ID) following them, the number of args and the length of the array may differ.
							{
								// These values are defined in ByteCodeExecSerializer.cs in the compiler.
								switch (intArray[t])
								{
									case 0: // ANYTHING
										break;
									case 1: // NULL
										if (values[int1].Type != Value.TYPE_NULL) bool1 = false;
										break;
									case 2: // INT
										if (values[int1].Type != Value.TYPE_INTEGER) bool1 = false;
										break;
									case 3: // FLOAT
										if (values[int1].Type != Value.TYPE_FLOAT) bool1 = false;
										break;
									case 4: // ANY NUMBER
										if (values[int1].Type != Value.TYPE_FLOAT && values[int1].Type != Value.TYPE_INTEGER) bool1 = false;
										break;
									case 5: // BOOL
										if (values[int1].Type != Value.TYPE_BOOLEAN) bool1 = false;
										break;
									case 6: // STRING
										if (values[int1].Type != Value.TYPE_STRING) bool1 = false;
										break;
									case 7: // LIST
										if (values[int1].Type != Value.TYPE_LIST) bool1 = false;
										break;
									case 8: // INSTANCE
										if (values[int1].Type != Value.TYPE_INSTANCE) bool1 = false;
										else
										{
											int3 = intArray[++t]; // required class ID or 0 for any class
											if (int3 != 0 && (int)values[int1].Value0 != int3)
											{
												bool1 = false;
											}
										}
										break;

									default: throw new Exception("This shouldn't happen.");
								}

								++int1;

								if (!bool1)
								{
									break;
								}
							}
						}
						else
						{
							bool1 = false;
						}

						if (!bool1)
						{
							// was not a match. jump according to the first param
							callStackTop.ProgramCounter += firstParam[pc];
						}

						break;

					case Interpreter.BC_ASSIGN:
						value = valueStack[--valueStackVirtualSize];
						assignmentTarget = valueStack[--valueStackVirtualSize];
						switch (assignmentTarget.Type)
						{
							case Value.TYPE_VARIABLE_POINTER:
								int1 = (int)assignmentTarget.Value0;
								bool1 = true; // absent from dictionary
								lvBucket = callStackTop.LocalVariables[int1 & 31];
								for (lvbWalker = lvBucket; lvbWalker != null; lvbWalker = lvbWalker.Next)
								{
									if (lvbWalker.Key == int1)
									{
										lvbWalker.Value = value;
										bool1 = false;
										break;
									}
								}

								if (bool1)
								{
									callStackTop.LocalVariables[int1 & 31] = new LocalVarBucket(int1, value, lvBucket);
								}
								break;

							case Value.TYPE_DOT_POINTER:
								value1 = (Value)assignmentTarget.Value0; // root
								int1 = (int)assignmentTarget.Value1; // step ID
								Dictionary<int, int[]> members;
								bool1 = value1.Type == Value.TYPE_INSTANCE; // is instance
								switch (value1.Type)
								{
									case Value.TYPE_INSTANCE:
									case Value.TYPE_CLASS:
										classId = (int)value1.Value0; // class ID
										valueLookup1 = (Dictionary<int, Value>)value1.Value1; // member values
										members = (bool1 ? classMemberLookupSet : staticClassMemberLookupSet)[classId];
										if (members.TryGetValue(int1, out intArray))
										{
											// intArray[1] is memberID
											switch (intArray[0]) // Member type ID: These values are defined at the top of MetadataSimplifier in the compiler.
											{
												case 2: // Field
												case 7: // Static field
													valueLookup1[int1] = value;
													break;

												case 5: // Property setter
												case 10: // Static property setter
													bool1 = intArray[0] == 10; // is static
													intArray = metadata.PropertySetters[intArray[1]];
													callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
													callStackTop = new StackContext(callStackTop, intArray[2] - 1 /* start location */, bool1 ? null : value1, new Value[] { value }, metadata);
													callStackTop.ReturnNothing = true;
													callStackModified = true;
													break;

												default:
													throw new Exception("This class member does not support assignment.");
											}
										}
										else
										{
											callStackTop = ThrowRuntimeException(metadata, callStackTop, "Class does not have a member named \"" + idTable[int1] + "\"", valueStackVirtualSize);
											callStackModified = true;
										}
										break;

									default:
										throw new NotImplementedException();
								}
								break;

							case Value.TYPE_BRACKET_POINTER:
								value1 = (Value)assignmentTarget.Value0; // root
								value2 = (Value)assignmentTarget.Value1; // index
								switch (value1.Type)
								{
									case Value.TYPE_LIST:
										list1 = (Value[])value1.Value0;
										if (value2.Type == Value.TYPE_INTEGER)
										{
											int1 = (int)value2.Value0;
											if (int1 < 0 || int1 >= list1.Length)
											{
												throw new Exception("index is out of range of the list.");
											}
											else
											{
												list1[int1] = value;
											}
										}
										else
										{
											throw new Exception("index of a list must be an integer.");
										}
										break;

									case Value.TYPE_MAP:
										map1 = (MapImpl)value1.Value0;
										map1.TryAdd(value2, value, true);
										break;

									default:
										throw new NotImplementedException("indexing into this type is not supported yet.");
								}
								break;

							default:
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Invalid target for assignment.", valueStackVirtualSize);
								callStackModified = true;
								break;
						}
						break;

					case Interpreter.BC_BASE_CLASS_CONS:
						int1 = firstParam[pc]; // number of args
						int2 = multiParam[pc][0]; // constructor ID
						if (int1 == 0)
						{
							values = EMPTY_VALUE_ARRAY;
						}
						else
						{
							values = new Value[int1]; // args
							while (int1 > 0)
							{
								values[--int1] = valueStack[--valueStackVirtualSize];
							}
						}
						callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
						callStackTop = new StackContext(callStackTop, metadata.Constructors[int2][1] - 1 /* start location */, callStackTop.ClassContext, values, metadata);
						// TODO: remove the pop caused by the compiler after base class constructors are called and then set this callStack to return nothing
						callStackModified = true;
						break;

					case Interpreter.BC_BOOLEAN_NOT:
						value = valueStack[--valueStackVirtualSize];
						if (value.Type == Value.TYPE_BOOLEAN)
						{
							valueStack[valueStackVirtualSize++] = (bool)value.Value0 ? Value.False : Value.True;
						}
						else
						{
							throw new Exception("! is not defined for non boolean types.");
						}
						break;

					case Interpreter.BC_BRACKET:
						value2 = valueStack[--valueStackVirtualSize];
						value1 = valueStack[--valueStackVirtualSize];
						switch (value1.Type)
						{
							case Value.TYPE_STRING:
								int1 = (int)value2.Value0; // index
								string1 = (string)value1.Value0;
								if (int1 >= 0 && int1 < string1.Length)
								{
									valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, "" + string1[int1]);
								}
								else
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "String index out of bounds.", valueStackVirtualSize);
									callStackModified = true;
								}
								break;

							case Value.TYPE_LIST:
								int1 = (int)value2.Value0; // index
								list1 = (Value[])value1.Value0;
								if (int1 >= 0 && int1 < (int)value1.Value1)
								{
									valueStack[valueStackVirtualSize++] = list1[int1];
								}
								else
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "List index out of bounds.", valueStackVirtualSize);
									callStackModified = true;
								}
								break;

							case Value.TYPE_MAP:
								Bucket b = ((MapImpl)value1.Value0).Get(value2, value2.ValueHash());
								if (b != null)
								{
									valueStack[valueStackVirtualSize++] = b.Value;
								}
								else
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "The map does not contain the key: " + value2.ForceString(metadata), valueStackVirtualSize);
									callStackModified = true;
								}
								break;

							case Value.TYPE_NATIVE_OBJECT:
							case Value.TYPE_INTEGER:
							case Value.TYPE_FUNCTION:
							case Value.TYPE_INSTANCE:
							case Value.TYPE_FLOAT:
							case Value.TYPE_CLASS:
							case Value.TYPE_BOOLEAN:
								callStackModified = true;
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Indexing is not allowed for the type: " + value1.GetTypeAsString(), valueStackVirtualSize);
								break;

							case Value.TYPE_NULL:
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Attempted to index into a null object.", valueStackVirtualSize);
								callStackModified = true;
								break;

							default:
								throw new NotImplementedException();
						}
						break;

					case Interpreter.BC_BRACKET_WRAPPED:
						valueStack = callStackTop.ValueStack;
						value2 = valueStack[--valueStackVirtualSize]; // index
						value1 = valueStack[--valueStackVirtualSize]; // root
						valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_BRACKET_POINTER, value1, value2);
						break;

					case Interpreter.BC_CLASS:
						classId = firstParam[pc];
						value = classes[classId];
						if ((int)value.Value2 == 2) // Class is fully initialized. It is safe to push it onto the value stack
						{
							callStackTop.ValueStack[valueStackVirtualSize++] = classes[classId];
						}
						else if ((int)value.Value2 == 1) // Class is in the process of getting initialized.
						{
							// setting fields and calling methods on the class from its static constructor will do this

							if (metadata.ClassInitializationStack.Peek() != classId)
							{
								// You are referring to a class that is currently being initialized, but not from that class itself.
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Staticly initialized fields created a cycle.", valueStackVirtualSize);
								callStackModified = true;
							}
							else
							{
								callStackTop.ValueStack[valueStackVirtualSize++] = classes[classId];
							}
						}
						else // value.Value2 == 0 // Class is not initialized. Treat this as a function call to the static constructor that has the class as a Value as a return value.
						{
							value.Value2 = 1; // Mark class as undergoing initialization.
							metadata.ClassInitializationStack.Push(classId);
							int1 = classBasicInfoTable[classId][6]; // static initializer method ID
							MethodSignature staticInitializer = metadata.StaticMethods[int1];

							// The class is passed as the context because all calls to the local class have been replaced with "this" at compile-time. 
							// Class dependencies (parents and base classes) are placed as references at the beginning of this initializer so walking up
							// the static initializer chain is done automatically by the nature of how this was compiled.
							// Isn't that cool?
							callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
							callStackTop = new StackContext(callStackTop, staticInitializer.StartLocation - 1, value, EMPTY_VALUE_ARRAY, metadata);
							callStackTop.ReturnContextInstead = true;
							callStackModified = true;
						}
						break;

					case Interpreter.BC_CREATE_LIST:
						int1 = firstParam[pc]; // list size
						// lists initialized without stuff in them are likely to be appended to, so start them with 10 capacity
						// lists with stuff in them are not likely to be expanded, so keep their allocation identical to their starting size
						if (int1 == 0)
						{
							valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_LIST, new Value[10], 0);
						}
						else
						{
							list1 = new Value[int1];
							while (int1 > 0)
							{
								list1[--int1] = valueStack[--valueStackVirtualSize];
							}
							valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_LIST, list1, list1.Length);
						}
						break;

					case Interpreter.BC_CREATE_MAP:
						int1 = firstParam[pc]; // map size
						int2 = int1 * 2;
						MapImpl map = new MapImpl(int1);
						values = new Value[int2];
						while (int2 > 0)
						{
							values[int2 - 1] = valueStack[--valueStackVirtualSize];
							--int2;
						}

						for (i = 0; i < values.Length; i += 2)
						{
							if (map.TryAdd(values[i], values[i + 1], false))
							{
								throw new Exception("Key collision");
							}
						}

						valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_MAP, map);
						break;

					case Interpreter.BC_DOT:
						callStack1 = callStackTop;
						callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
						callStackTop = Interpreter.DerefDot(metadata, callStackTop, firstParam[pc]);

						callStackModified = true;
						break;

					case Interpreter.BC_DOT_WRAPPED:
						// Swap out the root currently on the stack with the pointer without actually pushing or popping.
						valueStack[valueStackVirtualSize - 1] = new Value(
							Value.TYPE_DOT_POINTER,
							valueStack[valueStackVirtualSize - 1], // root
							firstParam[pc]); // step ID
						break;

					case Interpreter.BC_FALSE:
						callStackTop.ValueStack[valueStackVirtualSize++] = Value.False;
						break;

					case Interpreter.BC_FLOAT:
						callStackTop.ValueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, floatTable[firstParam[pc]]);
						break;

					case Interpreter.BC_CONSTRUCTOR_CALL:
					case Interpreter.BC_FUNCTION_CALL:
						bool1 = ops[pc] == Interpreter.BC_CONSTRUCTOR_CALL; // is constructor
						argCount = firstParam[pc];
						args = new Value[argCount];
						for (i = argCount - 1; i >= 0; --i)
						{
							args[i] = valueStack[--valueStackVirtualSize];
						}
						f = valueStack[--valueStackVirtualSize];
						if (bool1)
						{
							if (f.Type == Value.TYPE_CLASS)
							{
								int1 = classBasicInfoTable[(int)f.Value0][4]; // constructor ID

								// If no constructor is explicitly declared, one is implicitly created at compile-time, unless it's a static class.
								// Therefore 0 always indicates that this is a static class and should not be instantiated.
								if (int1 == 0)
								{
									callStackModified = true;
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "This class is static and cannot be instantiated.", valueStackVirtualSize);
								}
								else
								{
									intArray = constructorLookup[int1]; // constructor info

									if (intArray[2] < argCount)
									{
										callStackModified = true;
										callStackTop = ThrowRuntimeException(metadata, callStackTop, TooManyArgsErrorMessage("constructor", intArray[2], argCount), valueStackVirtualSize);
									}
									else
									{
										callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
										callStackTop = new StackContext(
											callStackTop,
											intArray[1] - 1, // start location
											Value.NewClassInstance((int)f.Value0),
											args,
											metadata);
										callStackTop.ReturnContextInstead = true;
										callStackModified = true;
									}
								}
							}
							else
							{
								callStackModified = true;
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Cannot invoke constructor on " + f.GetTypeAsString(), valueStackVirtualSize);
							}
						}
						else
						{
							if (f.Type == Value.TYPE_FUNCTION)
							{
								methodSignature = ((MethodSignature)f.Value0);
								if (methodSignature.ParamVars.Length < argCount)
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, TooManyArgsErrorMessage("method", methodSignature.ParamVars.Length, argCount), valueStackVirtualSize);
								}
								else
								{
									callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
									callStackTop = new StackContext(callStackTop, methodSignature.StartLocation - 1, (Value)f.Value1, args, metadata);
								}
								callStackModified = true;
							}
							else
							{
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Attempted to invoke " + f.GetTypeAsString() + " like a function.", valueStackVirtualSize);
								callStackModified = true;
							}
						}
						break;

					case Interpreter.BC_INCREMENT_POSTFIX:
					case Interpreter.BC_INCREMENT_PREFIX:
						value = valueStack[--valueStackVirtualSize];
						value1 = null; // original value
						value2 = null;
						bool1 = firstParam[pc] == 1; // 0 for --, 1 for ++
						bool2 = ops[pc] == Interpreter.BC_INCREMENT_POSTFIX; // true if first value should be returned
						if (value.Type == Value.TYPE_VARIABLE_POINTER)
						{
							int2 = (int)value.Value0; // variable ID
							bool3 = false; // found
							for (lvBucket = callStackTop.LocalVariables[int2 & 31]; lvBucket != null; lvBucket = lvBucket.Next)
							{
								if (lvBucket.Key == int2)
								{
									value = lvBucket.Value;
									bool3 = true;
									break;
								}
							}


							if (bool3)
							{
								if (value.Type == Value.TYPE_INTEGER)
								{
									int3 = (int)value.Value0 + (bool1 ? 1 : -1);
									if (int3 >= 0 && int3 < 1000)
									{
										value1 = integers[int3];
									}
									else
									{
										value1 = new Value(Value.TYPE_INTEGER, int3);
									}
									lvBucket.Value = value1;
									value2 = bool2 ? value : value1;
								}
								else
								{
									callStackModified = true;
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "Cannot apply " + (bool1 ? "++" : "--") + " to non-integer.", valueStackVirtualSize);
								}
							}
							else
							{
								callStackModified = true;
								callStackTop = ThrowRuntimeException(metadata, callStackTop, "Cannot use " + (bool1 ? "++" : "--") + " on unassigned variable.", valueStackVirtualSize);
							}
						}
						else if (value.Type == Value.TYPE_DOT_POINTER)
						{
							throw new NotImplementedException("This is complicated. Need to figure out a better approach without creating a problem that's O(language features)");
						}
						else
						{
							throw new NotImplementedException();
						}

						valueStack[valueStackVirtualSize++] = value2;
						break;

					case Interpreter.BC_INTEGER:
						valueStack[valueStackVirtualSize++] = Value.GetInteger(metadata.IntegerTable[firstParam[pc]]);
						break;

					case Interpreter.BC_INVOKE_STATIC_CONSTRUCTOR:
						int1 = firstParam[pc]; // class ID
						int2 = classBasicInfoTable[int1][3]; // static constructor ID
						intArray = metadata.StaticConstructors[int2]; // static constructor info
						int3 = intArray[1]; // start location
						callStackModified = true;
						callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
						callStackTop = new StackContext(callStackTop, int3 - 1, classes[int1], EMPTY_VALUE_ARRAY, metadata);
						break;

					case Interpreter.BC_JUMP:
						callStackTop.ProgramCounter += firstParam[pc];
						break;

					case Interpreter.BC_JUMP_IF_FALSE_NO_POP:
					case Interpreter.BC_JUMP_IF_FALSE:
					case Interpreter.BC_JUMP_IF_TRUE_NO_POP:
					case Interpreter.BC_JUMP_IF_TRUE:
						int1 = ops[pc];
						value = (int1 == Interpreter.BC_JUMP_IF_FALSE || int1 == Interpreter.BC_JUMP_IF_TRUE)
							? valueStack[--valueStackVirtualSize] // pop
							: valueStack[valueStackVirtualSize - 1]; // peek

						if (value.Type == Value.TYPE_BOOLEAN)
						{
							if (value == (int1 == Interpreter.BC_JUMP_IF_TRUE || int1 == Interpreter.BC_JUMP_IF_TRUE_NO_POP ? Value.True : Value.False))
							{
								callStackTop.ProgramCounter += firstParam[pc];
							}
						}
						else
						{
							callStackTop = ThrowRuntimeException(metadata, callStackTop, "Boolean expected. Found " + value.GetTypeAsString() + " instead.", valueStackVirtualSize);
							callStackModified = true;
						}
						break;

					case Interpreter.BC_JUMP_IF_NO_ARG_PRESENT:
						if (firstParam[pc] >= callStackTop.Args.Length)
						{
							pc += multiParam[pc][0];
						}
						break;

					case Interpreter.BC_KEEP_AND_DEREF_POINTER:
						value = valueStack[valueStackVirtualSize - 1];
						switch (value.Type)
						{
							case Value.TYPE_VARIABLE_POINTER:
								bool1 = false; // found
								int1 = (int)value.Value0; // variable ID
								for (lvBucket = callStackTop.LocalVariables[int1 & 31]; lvBucket != null; lvBucket = lvBucket.Next)
								{
									if (lvBucket.Key == int1)
									{
										value = lvBucket.Value;
										bool1 = true;
										break;
									}
								}

								if (bool1)
								{
									valueStack[valueStackVirtualSize++] = value;
								}
								else
								{
									// pc - 1 is used instead of pc since we want the error to point to the variable, not the modifying operation.
									--callStackTop.ProgramCounter;
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "Attempted to incrementally modify a value of an undeclared local variable.", valueStackVirtualSize);
									callStackModified = true;
								}
								break;

							case Value.TYPE_DOT_POINTER:
								valueStack[valueStackVirtualSize++] = (Value)value.Value0;
								callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
								callStackTop = Interpreter.DerefDot(metadata, callStackTop, (int)value.Value1);
								callStackModified = true;
								break;

							default:
								throw new NotImplementedException("In place incrementation against this type is not implemented yet.");
						}
						break;

					case Interpreter.BC_ADD:
					case Interpreter.BC_SUBTRACT:
					case Interpreter.BC_LESS_THAN:
					case Interpreter.BC_LESS_THAN_OR_EQUAL:
					case Interpreter.BC_GREATER_THAN:
					case Interpreter.BC_GREATER_THAN_OR_EQUAL:
					case Interpreter.BC_EQUALS:
					case Interpreter.BC_NOT_EQUAL:
					case Interpreter.BC_MOD:
					case Interpreter.BC_MULTIPLY:
					case Interpreter.BC_DIVIDE:
					case Interpreter.BC_BITWISE_AND:
					case Interpreter.BC_BITWISE_OR:
					case Interpreter.BC_BITWISE_XOR:
						numset = false;
						value2 = valueStack[--valueStackVirtualSize]; // right
						value1 = valueStack[--valueStackVirtualSize]; // left
						t = ((value1.Type * Value.TYPE_MAX) + value2.Type) * OperatorTableBuilder.OP_MAX + byteCodeToOp[ops[pc]];
						switch (opMap[t])
						{
							case 0: // not defined, by design
								if (value1.Type == Value.TYPE_NULL || value2.Type == Value.TYPE_NULL)
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "Null reference exception.", valueStackVirtualSize);
								}
								else
								{
									string exMessage = Util.GetInvalidOperatorExceptionMessage(OperatorTableBuilder.GetOpNameFromId(byteCodeToOp[ops[pc]]), value1, value2);
									callStackTop = ThrowRuntimeException(metadata, callStackTop, exMessage, valueStackVirtualSize);
								}
								callStackModified = true;
								break;

							case 1: /* int + int */ num = (int)value1.Value0 + (int)value2.Value0; numset = true; break;
							case 2: /* int + float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (int)value1.Value0 + (double)value2.Value0); break;
							case 3: /* float + int */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 + (int)value2.Value0); break;
							case 4: /* float + float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 + (double)value2.Value0); break;
							case 5: /* string + string */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (string)value1.Value0 + (string)value2.Value0); break;
							case 6: /* string + int */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (string)value1.Value0 + (int)value2.Value0); break;
							case 7: /* string + float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (string)value1.Value0 + (double)value2.Value0); break;
							case 8: /* string + bool */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (string)value1.Value0 + ((bool)value2.Value0 ? "true" : "false")); break;
							case 9: /* int + string */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (int)value1.Value0 + (string)value2.Value0); break;
							case 10: /* float + string */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, (double)value1.Value0 + (string)value2.Value0); break;
							case 11: /* bool + string */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, ((bool)value1.Value0 ? "true" : "false") + (string)value2.Value0); break;
							case 12: /* int - int */ num = (int)value1.Value0 - (int)value2.Value0; numset = true; break;
							case 13: /* int - float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (int)value1.Value0 - (double)value2.Value0); break;
							case 14: /* float - int */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 - (int)value2.Value0); break;
							case 15: /* float - float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 - (double)value2.Value0); break;
							case 16: /* int * int */ num = (int)value1.Value0 * (int)value2.Value0; numset = true; break;
							case 17: /* int * float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (int)value1.Value0 * (double)value2.Value0); break;
							case 18: /* float * int */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 * (int)value2.Value0); break;
							case 19: /* float * float */ valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 * (double)value2.Value0); break;
							case 20: /* int / int */
								int1 = (int)value1.Value0;
								int2 = (int)value2.Value0;
								if (int2 == 0)
								{
									callStackTop = ThrowRuntimeException(metadata, callStackTop, "Division by zero has occurred.", valueStackVirtualSize);
									callStackModified = true;
								}
								else
								{
									num = int1 / int2;
									if (int1 != int2 * num)
									{
										if ((int1 < 0) ^ (int2 < 0))
										{
											// in the case of negative division where there is some sort of round down:
											// - Python rounds down in value, -3.5 --> -4
											// - C# rounds down in magnitude, -3.5 --> -3
											// - Bb should copy Python. As this is C#, just subtract 1 to get the correct value.
											--num;
										}
									}
									numset = true;
								}
								break;
							case 21: /* int / float */
								if ((double)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (int)value1.Value0 / (double)value2.Value0);
								break;
							case 22: /* float / int */
								if ((int)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 / (int)value2.Value0);
								break;
							case 23: /* float / float */
								if ((double)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 / (double)value2.Value0);
								break;
							case 24: /* bool == bool */ valueStack[valueStackVirtualSize++] = ((bool)value1.Value0 == (bool)value2.Value0) ? Value.True : Value.False; break;
							case 25: /* int == int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 == (int)value2.Value0) ? Value.True : Value.False; break;
							case 26: /* int == float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 == (double)value2.Value0) ? Value.True : Value.False; break;
							case 27: /* float == int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 == (int)value2.Value0) ? Value.True : Value.False; break;
							case 28: /* float == float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 == (double)value2.Value0) ? Value.True : Value.False; break;
							case 29: /* string == string */ valueStack[valueStackVirtualSize++] = ((string)value1.Value0 == (string)value2.Value0) ? Value.True : Value.False; break;
							case 30: /* bool != bool */ valueStack[valueStackVirtualSize++] = ((bool)value1.Value0 == (bool)value2.Value0) ? Value.False : Value.True; break;
							case 31: /* int != int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 == (int)value2.Value0) ? Value.False : Value.True; break;
							case 32: /* int != float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 == (double)value2.Value0) ? Value.False : Value.True; break;
							case 33: /* float != int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 == (int)value2.Value0) ? Value.False : Value.True; break;
							case 34: /* float != float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 == (double)value2.Value0) ? Value.False : Value.True; break;
							case 35: /* string != string */ valueStack[valueStackVirtualSize++] = ((string)value1.Value0 == (string)value2.Value0) ? Value.False : Value.True; break;
							case 36: /* int < int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 < (int)value2.Value0) ? Value.True : Value.False; break;
							case 37: /* int < float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 < (double)value2.Value0) ? Value.True : Value.False; break;
							case 38: /* float < int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 < (int)value2.Value0) ? Value.True : Value.False; break;
							case 39: /* float < float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 < (double)value2.Value0) ? Value.True : Value.False; break;
							case 40: /* int <= int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 <= (int)value2.Value0) ? Value.True : Value.False; break;
							case 41: /* int <= float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 <= (double)value2.Value0) ? Value.True : Value.False; break;
							case 42: /* float <= int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 <= (int)value2.Value0) ? Value.True : Value.False; break;
							case 43: /* float <= float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 <= (double)value2.Value0) ? Value.True : Value.False; break;
							case 44: /* int > int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 > (int)value2.Value0) ? Value.True : Value.False; break;
							case 45: /* int > float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 > (double)value2.Value0) ? Value.True : Value.False; break;
							case 46: /* float > int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 > (int)value2.Value0) ? Value.True : Value.False; break;
							case 47: /* float > float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 > (double)value2.Value0) ? Value.True : Value.False; break;
							case 48: /* int >= int */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 >= (int)value2.Value0) ? Value.True : Value.False; break;
							case 49: /* int >= float */ valueStack[valueStackVirtualSize++] = ((int)value1.Value0 >= (double)value2.Value0) ? Value.True : Value.False; break;
							case 50: /* float >= int */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 >= (int)value2.Value0) ? Value.True : Value.False; break;
							case 51: /* float >= float */ valueStack[valueStackVirtualSize++] = ((double)value1.Value0 >= (double)value2.Value0) ? Value.True : Value.False; break;
							case 52: /* int << int */ valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 << (int)value2.Value0); break;
							case 53: /* int >> int */ valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 >> (int)value2.Value0); break;
							case 54: /* int & int */ valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 & (int)value2.Value0); break;
							case 55: /* int | int */ valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 | (int)value2.Value0); break;
							case 56: /* int ^ int */ valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 ^ (int)value2.Value0); break;

							case 57: /* list + list */
								int1 = (int)value1.Value1; // virtual length of first list
								int2 = (int)value2.Value1; // virtual length of second list
								int3 = int1 + int2; // virtual (and real) length of third list
								// the reason why no extra capacity padding is added here is because if someone is silly enough to use list + list, then they're likely
								// not going to suddenly switch to .add(). Extra padding will usually be wasted allocation.

								list1 = (Value[])value1.Value0; // first list
								list2 = (Value[])value2.Value0; // second list
								list3 = new Value[int3];
								for (i = 0; i < int1; ++i)
								{
									list3[i] = list1[i];
								}
								for (i = 0; i < int2; ++i)
								{
									list3[int1 + i] = list2[i];
								}

								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_LIST, list3, int3, int3);
								break;

							// TODO: these next two implementations are WRONG. This should not compare references, but rather, compare the contents of each.
							// In order to make things easy and dynamically use the op code table, this should be a primitive function
							// that is called into from here.
							case 58: /* list != list */
								list1 = (Value[])value1.Value0;
								list2 = (Value[])value2.Value0;
								valueStack[valueStackVirtualSize++] = list1 == list2 ? Value.False : Value.True;
								break;
							case 59: /* list == list */
								list1 = (Value[])value1.Value0;
								list2 = (Value[])value2.Value0;
								valueStack[valueStackVirtualSize++] = list1 == list2 ? Value.True : Value.False;
								break;

							case 60: /* int % int */
								if ((int)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = Value.GetInteger((int)value1.Value0 % (int)value2.Value0);
								break;

							case 61: /* int % float */
								if ((double)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (int)value1.Value0 % (double)value2.Value0);
								break;

							case 62: /* float % int */
								if ((int)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 % (int)value2.Value0);
								break;

							case 63: /* float % float */
								if ((double)value2.Value0 == 0) throw new Exception("Need to throw exception for division by 0");
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, (double)value1.Value0 % (double)value2.Value0);
								break;

							case 64: /* list + string */
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, value1.ForceString(metadata) + value2.Value0.ToString());
								break;

							case 65: /* string + list */
								valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_STRING, value1.Value0.ToString() + value2.ForceString(metadata));
								break;

							case 67: /* string * int */
							case 68: /* int * string */
								if (opMap[t] == 67)
								{
									int1 = (int)value2.Value0;
									string1 = (string)value1.Value0;
								}
								else
								{
									int1 = (int)value1.Value0;
									string1 = (string)value2.Value0;
								}
								if (int1 == 0) value = Value.EMPTY_STRING;
								else if (int1 == 1) value = value1;
								else
								{
									stringBuilder = new StringBuilder();
									while (int1 > 0)
									{
										stringBuilder.Append(string1);
										--int1;
									}
									value = new Value(Value.TYPE_STRING, stringBuilder.ToString());
								}
								valueStack[valueStackVirtualSize++] = value;
								break;

							case 69: /* list * int */
							case 70: /* int * list */
								if (opMap[t] == 69)
								{
									int1 = (int)value2.Value0; // multiplier
									list1 = (Value[])value1.Value0; // list array
									int2 = (int)value1.Value1; // list length
								}
								else
								{
									int1 = (int)value1.Value0; // multiplier
									list1 = (Value[])value2.Value0; // list array
									int2 = (int)value2.Value1; // list length
								}
								if (int1 == 0)
								{
									values = EMPTY_VALUE_ARRAY;
									value = new Value(Value.TYPE_LIST, values, 0);
								}
								else
								{
									int3 = int2 * int1; // final list length
									values = new Value[int3];

									for (i = 0; i < int3; i += int2)
									{
										for (j = 0; j < int2; ++j)
										{
											values[i + j] = list1[j];
										}
									}
									value = new Value(Value.TYPE_LIST, values, int3);
								}
								valueStack[valueStackVirtualSize++] = value;
								break;

							// Quick False
							case 1000000:
								valueStack[valueStackVirtualSize++] = Value.False;
								break;

							// Quick True
							case 1000001:
								valueStack[valueStackVirtualSize++] = Value.True;
								break;

							// Just do a comparison on Value0 with no casting
							case 1000002:
								valueStack[valueStackVirtualSize++] = (value1.Value0 == value2.Value0) ? Value.True : Value.False;
								break;

							// Opposite of above
							case 1000003:
								valueStack[valueStackVirtualSize++] = (value1.Value0 == value2.Value0) ? Value.False : Value.True;
								break;

							default:
								throw new Exception("Op behavior not defined, but it should be.");
						}

						if (numset)
						{
							valueStack[valueStackVirtualSize++] = (num < 1000 && num >= 0) ? integers[num] : new Value(Value.TYPE_INTEGER, num);
						}
						break;

					case Interpreter.BC_NATIVE_FUNCTION:
						int1 = firstParam[pc];
						int2 = multiParam[pc][0];
						values = null;
						if (int2 > 0)
						{
							values = new Value[int2];
							while (int2 > 0)
							{
								values[int2 - 1] = valueStack[--valueStackVirtualSize];
								--int2;
							}
						}
						returnValue = nativeFunctions[int1].Do(callStackTop, values);

						// This next line is necessary even if the returnValue is an exception. I don't know why since throw
						// in the if statement should clear the value stack. But it still crashes anyway. Meh.
						valueStack[valueStackVirtualSize++] = returnValue;

						if (returnValue.Type == Value.TYPE_INSTANCE &&
							ClassUtil.ExtendsFromSystemException(metadata, returnValue))
						{
							ThrowThis(metadata, callStackTop, returnValue, true);
						}
						break;

					case Interpreter.BC_NEGATIVE:
						value = valueStack[--valueStackVirtualSize];
						if (value.Type == Value.TYPE_INTEGER)
						{
							valueStack[valueStackVirtualSize++] = Value.GetInteger(-(int)value.Value0);
						}
						else if (value.Type == Value.TYPE_FLOAT)
						{
							valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_FLOAT, -(double)value.Value0);
						}
						else
						{
							string exMessage = value.Type == Value.TYPE_NULL
								? "Null reference exception."
								: ("Negative signs cannot be applied to type " + value.GetTypeAsString());
							callStackTop = ThrowRuntimeException(metadata, callStackTop, exMessage, valueStackVirtualSize);
							callStackModified = true;
						}
						break;

					case Interpreter.BC_NULL:
						callStackTop.ValueStack[valueStackVirtualSize++] = Value.Null;
						break;

					case Interpreter.BC_POP:
						--valueStackVirtualSize;
						break;

					case Interpreter.BC_RETURN:
						// TODO: you still need to check to see if the previous
						// stack frame had a bubbling exception on it. You can still return
						// from a finally block with an uncaught exception.
						returnValue = valueStack[--valueStackVirtualSize];
						if (callStackTop.Below == null)
						{
							// Thread has finished.
							return;
						}
						if (callStackTop.ReturnContextInstead)
						{
							returnValue = callStackTop.ClassContext;
						}
						if (callStackTop.InterpreterYieldUponPop)
						{
							// yeild back to whatever called this. The stack below is not yours to pop through.
							return;
						}
						callStackTop = callStackTop.Below;

						if (callStackTop.ValueStackVirtualSize == callStackTop.ValueStackRealSize)
						{
							throw new ThisShouldNeverHappenException("Hmm...apparently this is possible.");
						}

						callStackTop.ValueStack[callStackTop.ValueStackVirtualSize++] = returnValue;
						callStackModified = true;
						break;

					case Interpreter.BC_STRING:
						valueStack[valueStackVirtualSize++] = Value.GetString(metadata.StringTable[firstParam[pc]]);
						break;

					case Interpreter.BC_THIS:
						value = callStackTop.ClassContext;
						if (value == null)
						{
							// TODO: verify the compiler catches all of these and then remove this.
							callStackTop = ThrowRuntimeException(metadata, callStackTop, "Cannot refer to \"this\" in a static context.", valueStackVirtualSize);
							callStackModified = true;
						}
						else
						{
							valueStack[valueStackVirtualSize++] = value;
						}
						break;

					case Interpreter.BC_TRUE:
						valueStack[valueStackVirtualSize++] = Value.True;
						break;

					case Interpreter.BC_VARIABLE:
						int1 = firstParam[pc]; // variable ID
						bool1 = false; // found
						for (lvBucket = callStackTop.LocalVariables[int1 & 31]; lvBucket != null; lvBucket = lvBucket.Next)
						{
							if (lvBucket.Key == int1)
							{
								value = lvBucket.Value;
								bool1 = true;
								break;
							}
						}

						if (bool1)
						{
							valueStack[valueStackVirtualSize++] = value;
						}
						else
						{
							callStackModified = true;
							callStackTop = ThrowRuntimeException(metadata, callStackTop, "Referenced to unassigned variable: \"" + idTable[int1] + "\"", valueStackVirtualSize);
						}
						break;

					case Interpreter.BC_VARIABLE_WRAPPED:
						t = firstParam[pc];
						// TODO: cache these before execution begins and do an array lookup
						valueStack[valueStackVirtualSize++] = new Value(Value.TYPE_VARIABLE_POINTER, t);
						break;

					default:
						throw new ThisShouldNeverHappenException("Unknown op code: " + ops[pc]);
				}

				if (callStackModified)
				{
					if (callStackTop.SuddenQuit != 0)
					{
						if (callStackTop.SuddenQuit == 1)
						{
							return;
						}
						else if (callStackTop.SuddenQuit == 2)
						{
							return;
						}
					}
					valueStackVirtualSize = callStackTop.ValueStackVirtualSize;
					valueStackRealSize = callStackTop.ValueStackRealSize;
					valueStack = callStackTop.ValueStack;
					callStackModified = false;
				}

				++callStackTop.ProgramCounter;
			}
		}

		private static StackContext DerefDot(Metadata metadata, StackContext currentStackTop, int step)
		{
			Value root = currentStackTop.ValueStack[--currentStackTop.ValueStackVirtualSize];

			switch (root.Type)
			{
				case Value.TYPE_INSTANCE:
				case Value.TYPE_CLASS:
					int classId = (int)root.Value0; // class ID
					bool is_instance = root.Type == Value.TYPE_INSTANCE; // is instance
					Dictionary<int, int[]> memberLookup = (is_instance ? metadata.ClassMemberLookupGet : metadata.StaticClassMemberLookupGet)[classId];
					int[] intArray;
					if (memberLookup.TryGetValue(step, out intArray))
					{
						switch (intArray[0]) // Member ID is defined at the top of MetadataSimplifier.cs in the compiler
						{
							case 2: // field
							case 7: // static field
								Dictionary<int, Value> valueLookup = (Dictionary<int, Value>)root.Value1;
								currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = valueLookup[step]; // if the constructor worked properly, this value will always be present.
								return currentStackTop;

							case 3: // method
							case 8: // static method
								Value functionPointer = ClassUtil.GetMethod(metadata, classId, step, intArray[1], is_instance ? root : null);
								currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = functionPointer;
								return currentStackTop;

							case 4: // property getter
							case 9: // static property getter
								is_instance = intArray[0] == 9; // isStatic
								int propertyGetterId = intArray[1]; // Property Getter ID
								intArray = (is_instance ? metadata.StaticPropertyGetters : metadata.PropertyGetters)[propertyGetterId];

								return new StackContext(currentStackTop, intArray[2] - 1 /* start location */, is_instance ? null : root, EMPTY_VALUE_ARRAY, metadata);

							default:
								throw new ThisShouldNeverHappenException("Unrecognized field was accessed from a class or instance.");
						}
					}

					string rootTypeString = root.Type == Value.TYPE_CLASS ? "Class" : "Instance of";

					string exMessage = rootTypeString + " \"" + ClassUtil.GetName(metadata, classId) + "\" has no member called \"" + metadata.IdTable[step] + "\".";
					return ThrowRuntimeException(metadata, currentStackTop, exMessage, currentStackTop.ValueStackVirtualSize);

				case Value.TYPE_STRING:
					string rootValue = (string)root.Value0;
					switch (metadata.IdTable[step])
					{
						case "length":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = Value.GetInteger(rootValue.Length);
							return currentStackTop;

						case "split":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[10]], root);
							return currentStackTop;

						case "trim":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[11]], root);
							return currentStackTop;

						default:
							return ThrowRuntimeException(metadata, currentStackTop, "Strings do not have a \"" + metadata.IdTable[step] + "\" field.", currentStackTop.ValueStackVirtualSize);
					}

				// TODO: cache these function pointers so they don't have to be allocated each time they're referenced.
				case Value.TYPE_LIST:
					switch ((string)metadata.IdTable[step])
					{
						case "length":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = Value.GetInteger((int)root.Value1);
							return currentStackTop;

						case "add":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[0]], root);
							return currentStackTop;

						case "clear":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[1]], root);
							return currentStackTop;

						case "insert":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[5]], root);
							return currentStackTop;

						case "reverse":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[6]], root);
							return currentStackTop;

						case "addList":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[4]], root);
							return currentStackTop;

						case "join":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[9]], root);
							return currentStackTop;

						case "remove":
							currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[12]], root);
							return currentStackTop;

						default:
							return ThrowRuntimeException(metadata, currentStackTop, "Lists do not have a \"" + metadata.IdTable[step] + "\" field.", currentStackTop.ValueStackVirtualSize);
					}

				case Value.TYPE_MAP:
					Value value;
					switch ((string)metadata.IdTable[step])
					{
						case "add":
							value = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[7]], root);
							break;

						case "get":
							value = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[2]], root);
							break;

						case "contains":
							value = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[3]], root);
							break;

						case "clear":
							value = new Value(Value.TYPE_FUNCTION, metadata.Methods[metadata.PrimitiveMethodLookup[8]], root);
							break;

						case "size":
							value = Value.GetInteger(((MapImpl)root.Value0).Size);
							break;

						case "keys":
							value = ((MapImpl)root.Value0).BuildKeyList();
							break;

						case "values":
							value = ((MapImpl)root.Value0).BuildValueList();
							break;

						case "entries":
							value = ((MapImpl)root.Value0).BuildKeyValuePairList();
							break;

						default:
							return ThrowRuntimeException(metadata, currentStackTop, "Maps do not have a \"" + metadata.IdTable[step] + "\" field.", currentStackTop.ValueStackVirtualSize);
					}

					currentStackTop.ValueStack[currentStackTop.ValueStackVirtualSize++] = value;
					return currentStackTop;

				default:
					return ThrowRuntimeException(metadata, currentStackTop, root.GetTypeAsString() + " does not have any fields on it.", currentStackTop.ValueStackVirtualSize);
			}
		}

		private static StackContext ThrowThis(Metadata data, StackContext callStackTop, Value exceptionValue, bool useExistingStackTrace)
		{
			if (exceptionValue.Type == Value.TYPE_INSTANCE && ClassUtil.ExtendsFromSystemException(data, exceptionValue))
			{
				StackTrace trace = useExistingStackTrace
					? (StackTrace)ClassUtil.GetField(data, exceptionValue, "_0").Value0
					: new StackTrace(callStackTop);

				ClassUtil.SetField(data, exceptionValue, "_0", new Value(Value.TYPE_NATIVE_OBJECT, trace));

				while (callStackTop != null)
				{
					TryContext tryContext = callStackTop.TryContext;
					if (tryContext != null)
					{
						if (tryContext.CatchStart > 0) // There is a catch
						{
							if (callStackTop.ProgramCounter <= tryContext.CatchStart) // we're in the try block.
							{
								tryContext.BubblingException = exceptionValue;
								tryContext.BubblingExceptionCaught = true; // when try_pop is run, do nothing
								callStackTop.ProgramCounter = tryContext.CatchStart - 1;
								callStackTop.ValueStackVirtualSize = 0;
								return callStackTop;
							}
							else if (tryContext.FinallyStart > 0) // we're not in the try, and there is a finally block
							{
								if (callStackTop.ProgramCounter < tryContext.FinallyStart) // we're in the catch and we've thrown again, but there is a finally
								{
									tryContext.BubblingException = exceptionValue;
									tryContext.BubblingExceptionCaught = false;
									callStackTop.ProgramCounter = tryContext.FinallyStart - 1;
									callStackTop.ValueStackVirtualSize = 0;
									return callStackTop;
								}
								else // there is a try, catch, and a finally, but we've thrown in the finally. Continue walking up.
								{
									tryContext = tryContext.Next;
								}
							}
							else // there is no finally. Continue walking up.
							{
								tryContext = tryContext.Next;
							}
						}
						else if (tryContext.FinallyStart > 0) // There is no catch at all (but there's a finally)
						{
							if (callStackTop.ProgramCounter <= tryContext.FinallyStart) // There is a finally and we're in the try block
							{
								tryContext.BubblingException = exceptionValue;
								tryContext.BubblingExceptionCaught = false; // when try_pop is run, bubble up further
								callStackTop.ProgramCounter = tryContext.FinallyStart - 1;
								callStackTop.ValueStackVirtualSize = 0;
								return callStackTop;
							}
							else // There is a finally and we're in it. Continue walking up.
							{
								tryContext = tryContext.Next;
							}
						}
						// else { } // impossible. try must always have a catch or finally or both
					}
					else
					{
						// There is no try context. Must pop the call stack.

						if (callStackTop.InterpreterYieldUponPop)
						{
							callStackTop.SuddenQuit = 2;
							callStackTop.ReturnValue = exceptionValue; // Hmm...this is kind of a hack.
							return callStackTop;
						}
						else if (callStackTop.Below == null)
						{
							callStackTop.SuddenQuit = 1;
							string exceptionMessage = (string)ClassUtil.GetField(data, exceptionValue, "message").Value0;
							System.Console.WriteLine(exceptionMessage);
							System.Console.WriteLine(trace.Serialize());
							return callStackTop;
						}
						else
						{
							callStackTop = callStackTop.Below;
						}
					}
				}

				throw new Exception("Exception was unhandled by user: " + trace.Serialize());
			}
			else
			{
				// Throw an actual exception. An angry one.
				throw new NotImplementedException("Tried to throw something other than an exception instance.");
				// TODO: create a system function that throws this exception. Set the callStack to that function as though it was just called.
			}
		}

		// It is not necessary to save the value stack length before setting the output of this function to the new callStackTop
		// because when an exception is thrown and a call stack context is popped, the value stack is cleared anyway. Saves 1 line
		// of code per runtime exception type.
		internal static StackContext ThrowRuntimeException(Metadata metadata, StackContext callStackTop, string message, int valueStackVirtualSize)
		{
			callStackTop.ValueStackVirtualSize = valueStackVirtualSize;
			Value messageValue = new Value(Value.TYPE_STRING, message);
			return new StackContext(callStackTop, metadata.RuntimeExceptionInvoker - 1, null, new Value[] { messageValue }, metadata);
		}

		private static string TooManyArgsErrorMessage(string target, int expected, int actual)
		{
			string message;
			switch (expected)
			{
				case 0: message = "This " + target + " does not accept any arguments. "; break;
				case 1: message = "This " + target + " only accepts one argument. "; break;
				default: message = "This " + target + " only accepts " + expected + " arguments. "; break;
			}

			message += actual == 1
				? "One argument was passed."
				: (actual + " arguments were passed.");

			return message;
		}
	}
}
